﻿namespace ImageProcessing
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading;

    /// <summary>
    /// Class carrying all translation functions.
    /// </summary>
    public class Transformation
    {
        /// <summary>
        /// Translates the specified orginal image by the specified offset in x and y.
        /// </summary>
        /// <param name="orginalImage">The orginal image.</param>
        /// <param name="offsetY">The offset Y.</param>
        /// <param name="offsetX">The offset X.</param>
        /// <returns>
        /// 2D array of ImageStructure represent the transleted image
        /// </returns>
        public static ImageStructure[,] Translate(ImageStructure[,] orginalImage, int offsetY, int offsetX)
        {
            int height = orginalImage.GetLength(0);
            int width = orginalImage.GetLength(1);
            ImageStructure[,] result = new ImageStructure[height, width];

            int rowsPerThread = height / Environment.ProcessorCount;
            Semaphore s = new Semaphore(1, 1000);
            int minHeight = 0, maxHeight = 0;
            List<Thread> threadList = new List<Thread>();
            for (int i = 1; i < Environment.ProcessorCount; i++)
            {
                s.WaitOne();
                minHeight = i * rowsPerThread;
                maxHeight = (i + 1) * rowsPerThread;
                if (i == (Environment.ProcessorCount - 1))
                {
                    Thread thread = new Thread(delegate() { new Transformation().TranslateThreading(s, minHeight, height, width, height, orginalImage, result, offsetX, offsetY); });
                    thread.Start();
                    threadList.Add(thread);
                    break;
                }

                Thread t = new Thread(delegate() { new Transformation().TranslateThreading(s, minHeight, maxHeight, width, height, orginalImage, result, offsetX, offsetY); });
                t.Start();
                threadList.Add(t);
            }

            new Transformation().TranslateThreading(s, 0, rowsPerThread, width, height, orginalImage, result, offsetX, offsetY);
            foreach (Thread t in threadList)
            {
                t.Join();
            }

            return result;
        }

        /// <summary>
        /// Rotates the specified orginal image by the specified theta.
        /// </summary>
        /// <param name="originalImage">The original image.</param>
        /// <param name="theta">The theta.</param>
        /// <param name="width">The width.</param>
        /// <param name="height">The height.</param>
        /// <returns>
        /// 2D array of ImageStructure represent the rotated image
        /// </returns>
        public static ImageStructure[,] Rotate(ImageStructure[,] originalImage, double theta, int width, int height, bool videoProcessing)
        {
            double halfWidth = (((double)width) / 2.0) - 1;
            double halfHeight = (((double)height) / 2.0) - 1;
            double negTheta = -theta;
            if (theta == 0.0)
            {
                return originalImage;
            }

            while (theta < 0)
            {
                theta += 360;
            }

            while (negTheta < 0)
            {
                negTheta += 360;
            }

            if (theta > 360)
            {
                theta %= 360;
            }

            if (negTheta > 360)
            {
                negTheta %= 360;
            }

            theta = 180 - theta;
            negTheta = 180 - negTheta;

            double sinTheta = Math.Sin(theta);
            double cosTheta = Math.Cos(theta);

            double topRightX = (halfWidth * cosTheta) + (halfHeight * sinTheta);
            double topRightY = (-halfWidth * sinTheta) + (halfHeight * cosTheta);

            double topLeftX = (-halfWidth * cosTheta) + (halfHeight * sinTheta);
            double topLeftY = (halfWidth * sinTheta) + (halfHeight * cosTheta);

            double bottomLeftX = (-halfWidth * cosTheta) + (-halfHeight * sinTheta);
            double bottomLeftY = (halfWidth * sinTheta) + (-halfHeight * cosTheta);

            double bottomRightX = (halfWidth * cosTheta) + (-halfHeight * sinTheta);
            double bottomRightY = (-halfWidth * sinTheta) + (-halfHeight * cosTheta);

            double maxX, maxY;

            maxX = Math.Max(topRightX, topLeftX);
            maxX = Math.Max(maxX, bottomLeftX);
            maxX = Math.Max(maxX, bottomRightX);

            maxY = Math.Max(topRightY, topLeftY);
            maxY = Math.Max(maxY, bottomLeftY);
            maxY = Math.Max(maxY, bottomRightY);

            double newWidth = Math.Round((maxX - halfWidth) * 2) + width;
            newWidth++;
            double newHeight = Math.Round((maxY - halfHeight) * 2) + height;
            newHeight++;

            ImageStructure[,] result = new ImageStructure[(int)newHeight, (int)newWidth];

            int rowsPerThread = (int)newHeight / Environment.ProcessorCount;
            Semaphore s = new Semaphore(1, 1000);
            int minHeight, maxHeight;
            List<Thread> threadList = new List<Thread>();
            for (int i = 1; i < Environment.ProcessorCount; i++)
            {
                s.WaitOne();
                minHeight = i * rowsPerThread;
                maxHeight = (i + 1) * rowsPerThread;
                if (i == (Environment.ProcessorCount - 1))
                {
                    Thread thread = new Thread(delegate() { new Transformation().RotateThreading(s, minHeight, (int)newHeight, width, height, originalImage, result, negTheta, newWidth, newHeight); });
                    thread.Start();
                    threadList.Add(thread);
                    break;
                }

                Thread t = new Thread(delegate() { new Transformation().RotateThreading(s, minHeight, maxHeight, width, height, originalImage, result, negTheta, newWidth, newHeight); });
                threadList.Add(t);
                t.Start();
            }

            new Transformation().RotateThreading(s, 0, rowsPerThread, width, height, originalImage, result, negTheta, newWidth, newHeight); // return Operations.ReomveEmptyRows(Operations.ReomveEmptyColomns(result));
            foreach (Thread t in threadList)
            {
                t.Join();
            }

            if (!videoProcessing)
            {
                return Operations.RemoveEmptyColomns(Operations.RemoveEmptyRows(result));
            }

            return result;
        }

        public static void CalculateRotateNewSize(double theta, int width, int height, ref int newWidth, ref int newHeight)
        {
            double halfWidth = (((double)width) / 2.0) - 1;
            double halfHeight = (((double)height) / 2.0) - 1;
            double negTheta = -theta;
            if (theta == 0.0)
            {
                return;
            }

            while (theta < 0)
            {
                theta += 360;
            }

            while (negTheta < 0)
            {
                negTheta += 360;
            }

            if (theta > 360)
            {
                theta %= 360;
            }

            if (negTheta > 360)
            {
                negTheta %= 360;
            }

            theta = 180 - theta;
            negTheta = 180 - negTheta;

            double sinTheta = Math.Sin(theta);
            double cosTheta = Math.Cos(theta);

            double topRightX = (halfWidth * cosTheta) + (halfHeight * sinTheta);
            double topRightY = (-halfWidth * sinTheta) + (halfHeight * cosTheta);

            double topLeftX = (-halfWidth * cosTheta) + (halfHeight * sinTheta);
            double topLeftY = (halfWidth * sinTheta) + (halfHeight * cosTheta);

            double bottomLeftX = (-halfWidth * cosTheta) + (-halfHeight * sinTheta);
            double bottomLeftY = (halfWidth * sinTheta) + (-halfHeight * cosTheta);

            double bottomRightX = (halfWidth * cosTheta) + (-halfHeight * sinTheta);
            double bottomRightY = (-halfWidth * sinTheta) + (-halfHeight * cosTheta);

            double maxX, maxY;

            maxX = Math.Max(topRightX, topLeftX);
            maxX = Math.Max(maxX, bottomLeftX);
            maxX = Math.Max(maxX, bottomRightX);

            maxY = Math.Max(topRightY, topLeftY);
            maxY = Math.Max(maxY, bottomLeftY);
            maxY = Math.Max(maxY, bottomRightY);

            newWidth = (int)Math.Round((maxX - halfWidth) * 2) + width;
            newWidth++;
            newHeight = (int)Math.Round((maxY - halfHeight) * 2) + height;
            newHeight++;
        }

        /// <summary>
        /// Rotate90s to the Left.
        /// </summary>
        /// <param name="orginalImage">The orginal image.</param>
        /// <returns>
        /// 2D array of ImageStructure represent the rotated image
        /// </returns>
        public static ImageStructure[,] Rotate90Left(ImageStructure[,] orginalImage)
        {
            ImageStructure[,] result = new ImageStructure[orginalImage.GetLength(1), orginalImage.GetLength(0)];
            for (int j = result.GetLength(1) - 1; j >= 0; j--)
            {
                for (int i = 0; i < result.GetLength(0); i++)
                {
                    result[result.GetLength(0) - i - 1, j] = orginalImage[j, i];
                }
            }

            return result;
        }

        /// <summary>
        /// Rotate90s to the right.
        /// </summary>
        /// <param name="orginalImage">The orginal image.</param>
        /// <returns>2D array of ImageStructure represent the rotated image</returns>
        public static ImageStructure[,] Rotate90Right(ImageStructure[,] orginalImage)
        {
            ImageStructure[,] result = new ImageStructure[orginalImage.GetLength(1), orginalImage.GetLength(0)];
            for (int j = 0; j < result.GetLength(1); j++)
            {
                for (int i = 0; i < result.GetLength(0); i++)
                {
                    result[i, result.GetLength(1) - 1 - j] = orginalImage[j, i];
                }
            }

            return result;
        }

        /// <summary>
        /// Shears the on Y.
        /// </summary>
        /// <param name="image">The image.</param>
        /// <param name="theta">The theta.</param>
        /// <returns>Sheared matrix on Y.</returns>
        public static ImageStructure[,] ShearOnY(ImageStructure[,] image, double theta)
        {
            int width = image.GetLength(1);
            int height = image.GetLength(0);
            int newi, acc, newHeight;
            int extraHeight = (int)Math.Abs(width * (1 / Math.Tan(theta)));
            newHeight = extraHeight + height;

            ImageStructure[,] result = new ImageStructure[newHeight, width];

            if (Math.Tan(theta) < 0)
            {
                acc = (int)Math.Abs(width * (1 / Math.Tan(theta)));
            }
            else
            {
                acc = 0;
            }

            for (int i = 0; i < newHeight; i++)
            {
                for (int j = 0; j < width; j++)
                {
                    newi = (int)(i + (j * (1 / Math.Tan(theta))));

                    if ((newi + acc) >= newHeight || i >= height || j >= width)
                    {
                        continue;
                    }

                    try
                    {
                        result[newi + acc, j] = image[i, j];
                    }
                    catch
                    {
                        continue;
                    }
                }
            }

            return Operations.RemoveEmptyRows(result);
        }

        /// <summary>
        /// Shears the on X.
        /// </summary>
        /// <param name="image">The image.</param>
        /// <param name="theta">The theta.</param>
        /// <returns>Sheared image matrix.</returns>
        public static ImageStructure[,] ShearOnX(ImageStructure[,] image, double theta)
        {
            int width = image.GetLength(1);
            int height = image.GetLength(0);
            int newj, acc, newWidth;
            int extraWidth = (int)Math.Abs(height * (1 / Math.Tan(theta)));
            newWidth = extraWidth + width;

            ImageStructure[,] result = new ImageStructure[height, newWidth];

            if (Math.Tan(theta) < 0)
            {
                acc = (int)Math.Abs(height * (1 / Math.Tan(theta)));
            }
            else
            {
                acc = 0;
            }

            for (int i = 0; i < height; i++)
            {
                for (int j = 0; j < newWidth; j++)
                {
                    newj = (int)(j + (i * (1 / Math.Tan(theta))));
                    try
                    {
                        if ((newj + acc) >= newWidth || i >= height || j >= width)
                            continue;
                        result[i, newj + acc] = image[i, j];
                    }
                    catch (Exception ex)
                    {
                        Logger.LogException(ex);
                        continue;
                    }

                }
            }

            //return Operations.RemoveEmptyColomns(result);
            return result;
        }

        /// <summary>
        /// Translates the threading.
        /// </summary>
        /// <param name="s">The s.</param>
        /// <param name="minHeight">Height of the min.</param>
        /// <param name="maxHeight">Height of the max.</param>
        /// <param name="width">The width.</param>
        /// <param name="height">The height.</param>
        /// <param name="orginalImage">The orginal image.</param>
        /// <param name="result">The result.</param>
        /// <param name="offsetY">The offset Y.</param>
        /// <param name="offsetX">The offset X.</param>
        private void TranslateThreading(Semaphore s, int minHeight, int maxHeight, int width, int height, ImageStructure[,] orginalImage, ImageStructure[,] result, int offsetY, int offsetX)
        {
            s.Release();
            if (offsetX >= 0 && offsetY >= 0)
            {
                for (int i = minHeight; i < maxHeight; i++)
                {
                    for (int j = 0; j < width; j++)
                    {
                        result[(i + offsetY) % height, (j + offsetX) % width] = orginalImage[i, j];
                    }
                }
            }
            else if (offsetX < 0 && offsetY >= 0)
            {
                for (int j = 0; j < width; j++)
                {
                    for (int i = maxHeight - 1; i >= 0; i--)
                    {
                        result[(i + offsetX) < 0 ? (i + offsetX) + height : (i + offsetX), (j + offsetY) % width] = orginalImage[i, j];
                    }
                }
            }
            else if (offsetX >= 0 && offsetY < 0)
            {
                for (int j = result.GetLength(1) - 1; j >= 0; j--)
                {
                    for (int i = 0; i < maxHeight; i++)
                    {
                        result[(i + offsetX) % height, (j + offsetY) < 0 ? (j + offsetY) + width : (j + offsetY)] = orginalImage[i, j];
                    }
                }
            }
            else if (offsetX < 0 && offsetY < 0)
            {
                for (int j = width - 1; j >= 0; j--)
                {
                    for (int i = maxHeight - 1; i >= 0; i--)
                    {
                        result[(i + offsetX) < 0 ? (i + offsetX) + height : (i + offsetX), (j + offsetY) < 0 ? (j + offsetY) + width : (j + offsetY)] = orginalImage[i, j];
                    }
                }
            }
        }

        /// <summary>
        /// Rotates the threading.
        /// </summary>
        /// <param name="s">The s.</param>
        /// <param name="minHeight">Height of the min.</param>
        /// <param name="maxHeight">Height of the max.</param>
        /// <param name="width">The width.</param>
        /// <param name="height">The height.</param>
        /// <param name="originalImage">The original image.</param>
        /// <param name="result">The result.</param>
        /// <param name="negTheta">The neg theta.</param>
        /// <param name="newWidth">The new width.</param>
        /// <param name="newHeight">The new height.</param>
        private void RotateThreading(Semaphore s, int minHeight, int maxHeight, int width, int height, ImageStructure[,] originalImage, ImageStructure[,] result, double negTheta, double newWidth, double newHeight)
        {
            s.Release();
            double newHalfWidth = newWidth / 2;
            double newHalfHeight = newHeight / 2;
            double newX, newY, decimalX, decimalY;
            double topLeftX, topLeftY, topRightX, topRightY, bottomRightX, bottomRightY, bottomLeftX, bottomLeftY;
            double cosTheta = Math.Cos(negTheta);
            double sinTheta = Math.Sin(negTheta);
            double x, y, topRed, topGreen, topBlue, bottomRed, bottomGreen, bottomBlue;
            for (int i = minHeight; i < maxHeight; i++)
            {
                for (int j = 0; j < newWidth; j++)
                {
                    x = j - newHalfWidth;
                    y = i - newHalfHeight;

                    decimalX = (x * cosTheta) + (y * sinTheta) + (width / 2);
                    decimalY = (-1 * x * sinTheta) + (y * cosTheta) + (height / 2);

                    newX = Math.Round(decimalX);
                    newY = Math.Round(decimalY);

                    if (newX < 0 || newX >= width || newY < 0 || newY >= height)
                    {
                        continue;
                    }

                    topLeftX = Math.Floor(decimalX) < 0 ? 0 : Math.Floor(decimalX);
                    topLeftY = Math.Ceiling(decimalY) < 0 ? 0 : Math.Ceiling(decimalY);

                    topRightX = Math.Ceiling(decimalX) < 0 ? 0 : Math.Ceiling(decimalX);
                    topRightY = Math.Ceiling(decimalY) < 0 ? 0 : Math.Ceiling(decimalY);

                    bottomRightX = Math.Ceiling(decimalX) < 0 ? 0 : Math.Ceiling(decimalX);
                    bottomRightY = Math.Floor(decimalY) < 0 ? 0 : Math.Floor(decimalY);

                    bottomLeftX = Math.Floor(decimalX) < 0 ? 0 : Math.Floor(decimalX);
                    bottomLeftY = Math.Floor(decimalY) < 0 ? 0 : Math.Floor(decimalY);

                    topLeftX = topLeftX >= width ? width - 1 : topLeftX;
                    topLeftY = topLeftY >= height ? height - 1 : topLeftY;

                    topRightX = topRightX >= width ? width - 1 : topRightX;
                    topRightY = topRightY >= height ? height - 1 : topRightY;

                    bottomRightX = bottomRightX >= width ? width - 1 : bottomRightX;
                    bottomRightY = bottomRightY >= height ? height - 1 : bottomRightY;

                    bottomLeftX = bottomLeftX >= width ? width - 1 : bottomLeftX;
                    bottomLeftY = bottomLeftY >= height ? height - 1 : bottomLeftY;

                    decimalX -= newX;
                    decimalY -= newY;

                    topRed = Math.Round(((1 - decimalX) * originalImage[(int)topLeftY, (int)topLeftX].R) + (decimalX * originalImage[(int)topRightY, (int)topRightX].R));
                    topGreen = Math.Round(((1 - decimalX) * originalImage[(int)topLeftY, (int)topLeftX].G) + (decimalX * originalImage[(int)topRightY, (int)topRightX].G));
                    topBlue = Math.Round(((1 - decimalX) * originalImage[(int)topLeftY, (int)topLeftX].B) + (decimalX * originalImage[(int)topRightY, (int)topRightX].B));
                    bottomRed = Math.Round(((1 - decimalX) * originalImage[(int)bottomLeftY, (int)bottomLeftX].R) + (decimalX * originalImage[(int)bottomRightY, (int)bottomRightX].R));
                    bottomGreen = Math.Round(((1 - decimalX) * originalImage[(int)bottomLeftY, (int)bottomLeftX].G) + (decimalX * originalImage[(int)bottomRightY, (int)bottomRightX].G));
                    bottomBlue = Math.Round(((1 - decimalX) * originalImage[(int)bottomLeftY, (int)bottomLeftX].B) + (decimalX * originalImage[(int)bottomRightY, (int)bottomRightX].B));

                    topRed = Math.Round(((1 - decimalY) * topRed) + (decimalY * bottomRed));
                    topGreen = Math.Round(((1 - decimalY) * topGreen) + (decimalY * bottomGreen));
                    topBlue = Math.Round(((1 - decimalY) * topBlue) + (decimalY * bottomBlue));

                    if (topRed < 0)
                    {
                        topRed = 0;
                    }
                    else if (topRed > 255)
                    {
                        topRed = 255;
                    }

                    if (topGreen < 0)
                    {
                        topGreen = 0;
                    }
                    else if (topGreen > 255)
                    {
                        topGreen = 255;
                    }

                    if (topBlue < 0)
                    {
                        topBlue = 0;
                    }
                    else if (topBlue > 255)
                    {
                        topBlue = 255;
                    }

                    result[i, j].R = (byte)topRed;
                    result[i, j].G = (byte)topGreen;
                    result[i, j].B = (byte)topBlue;
                }
            }
        }

        public static void GetWidth_Height_ShearY(ImageStructure[,] image, float theta, ref int newwidth, ref int newheight)
        {
            int width = image.GetLength(1);
            int height = image.GetLength(0);
            int extraHeight = (int)Math.Abs(width * (1 / Math.Tan(theta)));
            newheight = extraHeight + height;
            newwidth = width;
        }

        public static void GetWidth_Height_ShearX(ImageStructure[,] image, float theta, ref int newwidth, ref int newheight)
        {
            int width = image.GetLength(1);
            int height = image.GetLength(0);
            int extraWidth = (int)Math.Abs(height * (1 / Math.Tan(theta)));
            newwidth = extraWidth + width;
            newheight = height;
        }
    }
}
